// SQLite implementation of simple database layer
// Copyright 2006 (C) Ralph Thomas

#include <util/db.h>
#include <util/cow_vector.h>
#include <util/concepts/list.h>
#include <toolkit/application.h>
#include <sqlite3.h>
#include <boost/filesystem/operations.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include <boost/algorithm/string.hpp>
#include <util/concepts/list.h>
#include <vector>
#include <map>

namespace bfs = boost::filesystem;

namespace util {
	struct column_t {
		virtual ~column_t() {}
		virtual void add_row(sqlite3_stmt*) = 0;
		virtual adobe::value_t get() const = 0;
		virtual unsigned int count() const = 0;
	};
	template<typename T>
	void add_row_fn(sqlite3_stmt*, int, std::vector<T>&) {
		//
		// This default version should never be called. Only the specializations
		// should be called.
		//
		throw std::runtime_error( "db add_row; no specialization for type!" );
	}
	template<>
	void add_row_fn<int>(sqlite3_stmt* s, int col, std::vector<int>& target)
	{
		target.push_back(sqlite3_column_int(s, col));
	}
	template<>
	void add_row_fn<std::string>(sqlite3_stmt* s, int col, std::vector<std::string>& target)
	{
		//
		// XXX: The return value from sqlite3_column_text is const, indicating
		// XXX: that we don't have to free it (and I'd hope not too, as all of
		// XXX: those allocs and frees take time!). Should check this out though
		// XXX: because I'm not sure.
		//
		target.push_back(std::string((const char*)sqlite3_column_text(s, col)));
	}
	template<>
	void add_row_fn<double>(sqlite3_stmt* s, int col, std::vector<double>& target)
	{
		target.push_back(sqlite3_column_double(s, col));
	}
	
	template<typename T>
	struct actual_column_t : public column_t {
		typedef std::vector<T> storage_t;
		int 		column_number_m;
		storage_t	rows_m;
		
		actual_column_t(int number)
		 : column_number_m(number) {}
		virtual void add_row(sqlite3_stmt* statement) {
			add_row_fn<T>(statement, column_number_m, rows_m);
		}
		virtual adobe::value_t get() const {
				return adobe::value_t( typename util::cow_vector<T>::type( rows_m ) );
		}
		virtual unsigned int count() const {
			return rows_m.size();
		}
	};


	struct result_set_t {
		typedef std::map<std::string, column_t*> column_set_t;
		column_set_t columns_m;
		
		result_set_t(sqlite3_stmt* statement) {
			int column_count = sqlite3_column_count(statement);
			for( int i = 0; i < column_count; i++ ) {
				//
				// XXX: Do we have to free this?
				//
				std::string name( sqlite3_column_name(statement, i) );
				//
				// Now create an actual column implementation for each column
				// of data.
				//
				std::string column(sqlite3_column_decltype(statement, i));
				if( column == "INTEGER" )
					columns_m[name] = new actual_column_t<int>(i);
				else if( column == "TEXT" )
					columns_m[name] = new actual_column_t<std::string>(i);
				else if( column == "NUMERIC" || column == "REAL" )
					columns_m[name] = new actual_column_t<double>(i);
				else
					throw std::runtime_error( "Unknown column type: " + column );
			}
		}
		virtual ~result_set_t() {
			for( column_set_t::const_iterator i = columns_m.begin(); i != columns_m.end(); i++ )
				delete i->second;
		}
		void add_row( sqlite3_stmt* statement ) {
			for( column_set_t::iterator i = columns_m.begin(); i != columns_m.end(); i++ )
				i->second->add_row(statement);
		}
		unsigned int results(adobe::dictionary_t& r) const {
			unsigned int count = 0;
			for( column_set_t::const_iterator i = columns_m.begin(); i != columns_m.end(); i++ ) {
				r.insert( adobe::dictionary_t::value_type(adobe::name_t(i->first.c_str()), i->second->get() ));
				count = i->second->count();
			}
			return count;
		}
	};

	struct dbImpl {
		typedef std::map<std::string, boost::weak_ptr<db> >		dbMap;
		typedef boost::recursive_mutex::scoped_lock	lock;
		typedef boost::recursive_mutex			mutex;
		//
		// If there are many different db instances over the lifetime
		// of the app then it's possible that this dbMap_s could become
		// large with dead weak pointers...
		//
		static dbMap dbMap_s;	///< Live db instances.
		
		dbImpl( sqlite3* data ) : database_m( data ) {}
		~dbImpl() {
			lock lk( mutex_m );
			if( !database_m ) return;
			sqlite3_close( database_m );
		}
		static void update( void* impl, int, const char* db_name, const char* table_name, sqlite_int64 ) {
			dbImpl* us( reinterpret_cast<dbImpl*>( impl ) );
			if( !us ) return;
			(us->on_change_m)();
		}
		static void signal_change( boost::weak_ptr<db> outer ) {
			db_ptr_t us( outer.lock() );
			if( !us ) return;
			us->_impl->on_change_m();
		}
		boost::weak_ptr<db>	us_m;
		sqlite3*		database_m;
		std::string		map_key_m;
		boost::signal<void()>	on_change_m;
		mutex			mutex_m;
	};
	dbImpl::dbMap dbImpl::dbMap_s;
	//
	/// Return a key into the static dbImpl::dbMap_s for the given
	/// database file path.
	///
	/// \param	p	the path to get the key for.
	/// \return	the key for the given path.
	//
	static std::string getMapKey( const bfs::path& p ) {
		return p.native_file_string();
	}
	
	static int process_row( void* results, int argc, char** argv, char** colName ) {
		db::on_row_proc_t* on_row_proc( reinterpret_cast<db::on_row_proc_t*>( results ) );
		if( !on_row_proc ) return 0;
		adobe::dictionary_t dict;
		for( int i = 0; i < argc; i++ ) {
			adobe::dictionary_t::value_type item;
			const char* column_cstr( colName[i] );
			const char* value_cstr( argv[i] );

			item.first = adobe::name_t( column_cstr );
			item.second = adobe::value_t( std::string( value_cstr ) );
			dict.insert( item );
		}
		if( *on_row_proc ) (*on_row_proc)( dict );
		return 0;
	}
	
	db::db( const boost::filesystem::path& path, bool create ) : _impl( 0 )
	{
		if( !create && !bfs::exists( path ) )
			throw std::runtime_error( path.native_file_string() + " does not exist" );
		sqlite3* database( 0 );
		int rc( sqlite3_open( path.native_file_string().c_str(), &database ) );
		if( rc != SQLITE_OK )
			throw std::runtime_error( path.native_file_string() + ": can't open" );
		_impl = new dbImpl( database );
		_impl->map_key_m = getMapKey( path );
	}
	db::~db() {
		if( _impl ) delete _impl;
	}

	db_ptr_t db::fromPath( const boost::filesystem::path& p ) {
		//
		// 1. Try to find this db in the dbImpl::dbMap_s.
		// 2. If it exists, retain and return it.
		// 3. If not, attempt to create a new db for it.
		// 4. Add it to the dbMap and return it.
		//
		db_ptr_t result;
		std::string key( getMapKey( p ) );
		dbImpl::dbMap::iterator i = dbImpl::dbMap_s.find( key );
		if( i != dbImpl::dbMap_s.end() ) {
			result = i->second.lock();
			if( result ) return result;
		}
		//
		// Create a new db. This will throw if it fails,
		// let the exception pass on up.
		//
		result = db_ptr_t( new db( p, true ) );
		//
		// Need a back-weak-pointer so that callbacks later
		// don't fire if the database has gone away. Also
		// need to abstract this pattern out as model would
		// benefit from it.
		//
		result->_impl->us_m = result;
		dbImpl::dbMap_s[key] = result;
		//
		// We already have a refcount of 1 just for being
		// created, don't increment it again.
		//
		return result;
	}
	long long int db::execute( const std::string& query ) {
		if( !_impl || !_impl->database_m )
			throw std::runtime_error( "no database open" );
		dbImpl::lock lk( _impl->mutex_m );
		char* error(0);
		int rc( sqlite3_exec( _impl->database_m, query.c_str(), 0, 0, &error ) );
		if( rc != SQLITE_OK ) {
			std::string msg;
			if( error ) {
				msg = error;
				sqlite3_free( error );
			}
			throw std::runtime_error( "SQLite: " + msg );
		}
		//
		// As we don't have sqlite3_update_hook available, just assume
		// that all executes cause a change to the db. We addref because
		// our caller may have released before the main-loop gets back
		// to signal_change.
		//
		toolkit::application::getInstance().executeInMain( boost::bind( &dbImpl::signal_change, _impl->us_m ) );
		return sqlite3_last_insert_rowid( _impl->database_m );
	}
	void db::query( const std::string& query, on_row_proc_t on_row ) {
		if( !_impl || !_impl->database_m )
			throw std::runtime_error( "no database open" );
		dbImpl::lock lk( _impl->mutex_m );
		char* error(0);
		int rc( sqlite3_exec( _impl->database_m, query.c_str(), &process_row, &on_row, &error ) );
		if( rc != SQLITE_OK ) {
			std::string msg;
			if( error ) {
				msg = error;
				sqlite3_free( error );
			}
			throw std::runtime_error( "SQLite: " + msg );
		}
	}
	unsigned int db::query( const std::string& query, adobe::dictionary_t& d ) {
		if( !_impl || !_impl->database_m )
			throw std::runtime_error( "no database open" );
		dbImpl::lock lk( _impl->mutex_m );
		//
		// Read in the SQL to get a statement structure built.
		// We can then use the statement structure with sqlite_step.
		//
		sqlite3_stmt* statement(0);
		const char* tail(0);
		int rc( sqlite3_prepare( _impl->database_m, query.c_str(), query.length(), &statement, &tail ) );
		if( rc != SQLITE_OK || !statement ) {
			if( statement ) sqlite3_finalize( statement );
			throw std::runtime_error( "SQLite: query cannot be interpreted: " + query );
		}
		//
		// We need to prepare the datastructure that we will push
		// results into.
		//
		result_set_t results(statement);
		//
		// Now we can use sqlite_step to execute the query and get the
		// individual rows back.
		//
		while( rc != SQLITE_ERROR && rc != SQLITE_DONE ) {
			rc = sqlite3_step( statement );
			if( rc == SQLITE_ROW ) results.add_row( statement );
		}
		//
		// We're done querying the database.
		//
		sqlite3_finalize( statement );
		if( rc == SQLITE_ERROR )
			throw std::runtime_error( "Unable to complete query: " + query );
		return results.results( d );
	}
	boost::signals::connection db::on_change( const change_proc_t& proc ) {
		if( !_impl || !_impl->database_m )
			throw std::runtime_error( "no database open" );
		return _impl->on_change_m.connect( proc );
		//
		// XXX: sqlite3_update_hook is unavailable on the version of sqlite3
		// XXX: that ships with ubuntu :(.
		//
		//sqlite3_update_hook( _impl->database_m, &dbImpl::update, _impl );
	}
	std::string db::escape( const std::string& s ) {
		return boost::replace_all_copy(s, "'", "''");
	}
};
